package personal.jinhui.paprika;

import java.security.GeneralSecurityException;
import javax.net.ssl.SSLContext;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Log;

import personal.jinhui.paprika.service.aidl.IXmppFacade;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.provider.DelayInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.pubsub.provider.EventProvider;
import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
import org.jivesoftware.smackx.pubsub.provider.ItemsProvider;
import org.jivesoftware.smackx.pubsub.provider.PubSubProvider;

import personal.jinhui.paprika.service.XmppConnectionAdapter;
import personal.jinhui.paprika.service.XmppFacade;
import personal.jinhui.paprika.smack.avatar.AvatarMetadataProvider;
import personal.jinhui.paprika.smack.avatar.AvatarProvider;
import personal.jinhui.paprika.smack.caps.CapsProvider;
import personal.jinhui.paprika.smack.ping.PingExtension;
import personal.jinhui.paprika.utils.PaprikaBroadcastReceiver;
import personal.jinhui.paprika.utils.PaprikaConnectivity;
import personal.jinhui.paprika.utils.Status;

/**
 * This class is for the Paprika service. It must contains every global
 * informations needed to maintain the background service. The connection to the
 * xmpp server will be made asynchronously when the service will start.
 */
public class PaprikaService extends Service {

	/** The id to use for status notification. */
	public static final int NOTIFICATION_STATUS_ID = 100;

	private static final String TAG = "PaprikaService";
	private static final int DEFAULT_XMPP_PORT = 5222;
	// private static final String COMMAND_NAMESPACE =
	// "http://jabber.org/protocol/commands";

	private NotificationManager mNotificationManager;
	private XmppConnectionAdapter mConnection;
	private SharedPreferences mSettings;
	private String mUsername;
	private String mPassword;
	private String mHost;
	private String mServiceName;
	private int mPort;
	private ConnectionConfiguration mConnectionConfiguration;
	private ProxyInfo mProxyInfo;
	private boolean mUseProxy;
	private IXmppFacade.Stub mBind;

	private PaprikaBroadcastReceiver mReceiver = new PaprikaBroadcastReceiver();
	private PaprikaServiceBroadcastReceiver mOnOffReceiver = new PaprikaServiceBroadcastReceiver();
	private PaprikaServicePreferenceListener mPreferenceListener = new PaprikaServicePreferenceListener();

	private boolean mOnOffReceiverIsRegistered;

	/**
	 * Constructor.
	 */
	public PaprikaService() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IBinder onBind(Intent intent) {
		Log.d(TAG, "ONBIND()");
		return mBind;
	}

	@Override
	public boolean onUnbind(Intent intent) {
		Log.d(TAG, "ONUNBIND()");
		if (!mConnection.getAdaptee().isConnected()) {
			this.stopSelf();
		}
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onCreate() {
		super.onCreate();
		registerReceiver(mReceiver, new IntentFilter(
				ConnectivityManager.CONNECTIVITY_ACTION));
		mSettings = PreferenceManager.getDefaultSharedPreferences(this);
		mSettings.registerOnSharedPreferenceChangeListener(mPreferenceListener);
		if (mSettings.getBoolean(PaprikaApplication.USE_AUTO_AWAY_KEY, false)) {
			mOnOffReceiverIsRegistered = true;
			registerReceiver(mOnOffReceiver, new IntentFilter(
					Intent.ACTION_SCREEN_OFF));
			registerReceiver(mOnOffReceiver, new IntentFilter(
					Intent.ACTION_SCREEN_ON));
		}

		mUsername = mSettings.getString(PaprikaApplication.USERNAME_KEY, "");
		mPassword = mSettings.getString(PaprikaApplication.PASSWORD_KEY, "");
		// mHost = mSettings.getString("xmpp_server", "").trim();
		mHost = mSettings.getString("xmpp_server", "");
		mServiceName = "paprikaserver.org";
		String tmpPort = mSettings.getString("xmpp_port", "5222");
		if (!"".equals(tmpPort))
			mPort = Integer.valueOf(tmpPort);
		else
			mPort = DEFAULT_XMPP_PORT;

		mUseProxy = mSettings.getBoolean(PaprikaApplication.PROXY_USE_KEY,
				false);
		if (mUseProxy) {
			String stype = mSettings.getString(
					PaprikaApplication.PROXY_TYPE_KEY, "HTTP");
			String phost = mSettings.getString(
					PaprikaApplication.PROXY_SERVER_KEY, "");
			String puser = mSettings.getString(
					PaprikaApplication.PROXY_USERNAME_KEY, "");
			String ppass = mSettings.getString(
					PaprikaApplication.PROXY_PASSWORD_KEY, "");
			int pport = Integer.parseInt(mSettings.getString(
					PaprikaApplication.PROXY_PORT_KEY, "1080"));
			ProxyInfo.ProxyType type = ProxyType.valueOf(stype);
			mProxyInfo = new ProxyInfo(type, phost, pport, puser, ppass);
		} else {
			mProxyInfo = ProxyInfo.forNoProxy();
		}
		mConnectionConfiguration = new ConnectionConfiguration(mHost, mPort,
				mServiceName, mProxyInfo);

		Log.d(TAG, mHost+", "+mPort+", "+mServiceName);
		
		if (mSettings.getBoolean("settings_key_xmpp_tls_use", false)
				|| mSettings.getBoolean("settings_key_gmail", false)) {
			mConnectionConfiguration.setSecurityMode(SecurityMode.required);
		}
		if (mSettings.getBoolean(PaprikaApplication.SMACK_DEBUG_KEY, false))
			mConnectionConfiguration.setDebuggerEnabled(true);
		mConnectionConfiguration.setSendPresence(false);
		// maybe not the universal path, but it works on most devices (Samsung
		// Galaxy, Google Nexus One)
		mConnectionConfiguration.setTruststoreType("BKS");
		mConnectionConfiguration
				.setTruststorePath("/system/etc/security/cacerts.bks");

		if (mSettings.getBoolean(PaprikaApplication.FULL_JID_LOGIN_KEY, false)
				|| "gmail.com".equals(mServiceName)
				|| "googlemail.com".equals(mServiceName)) {
			mUsername = mUsername + "@" + mHost;
		}

		configure(ProviderManager.getInstance());

		mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		mConnection = new XmppConnectionAdapter(mConnectionConfiguration,
				mUsername, mPassword, this);

		Roster.setDefaultSubscriptionMode(SubscriptionMode.manual);
		mBind = new XmppFacade(mConnection);
		Log.d(TAG, "Create PaprikaService");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onDestroy() {
		super.onDestroy();
		mNotificationManager.cancelAll();
		unregisterReceiver(mReceiver);
		mSettings
				.unregisterOnSharedPreferenceChangeListener(mPreferenceListener);
		if (mOnOffReceiverIsRegistered)
			unregisterReceiver(mOnOffReceiver);
		if (mConnection.isAuthentificated()
				&& PaprikaConnectivity.isConnected(this))
			mConnection.disconnect();
		Log.i(TAG, "Stopping the service");
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		Log.d(TAG, "onStart");
		try {
			mConnection.connectAsync();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Show a notification using the preference of the user.
	 * 
	 * @param id
	 *            the id of the notification.
	 * @param notif
	 *            the notification to show
	 */
	public void sendNotification(int id, Notification notif) {
		if (mSettings.getBoolean(PaprikaApplication.NOTIFICATION_VIBRATE_KEY,
				true))
			notif.defaults |= Notification.DEFAULT_VIBRATE;
		notif.ledARGB = 0xff0000ff; // Blue color
		notif.ledOnMS = 1000;
		notif.ledOffMS = 1000;
		notif.defaults |= Notification.DEFAULT_LIGHTS;
		String ringtoneStr = mSettings.getString(
				PaprikaApplication.NOTIFICATION_SOUND_KEY,
				Settings.System.DEFAULT_NOTIFICATION_URI.toString());
		notif.sound = Uri.parse(ringtoneStr);
		mNotificationManager.notify(id, notif);
	}

	/**
	 * Delete a notification.
	 * 
	 * @param id
	 *            the id of the notification
	 */
	public void deleteNotification(int id) {
		mNotificationManager.cancel(id);
	}

	/**
	 * Reset the status to online after a disconnect.
	 */
	public void resetStatus() {
		Editor edit = mSettings.edit();
		edit.putInt(PaprikaApplication.STATUS_KEY, 1);
		edit.commit();
	}

	/**
	 * Return a bind to an XmppFacade instance.
	 * 
	 * @return IXmppFacade a bind to an XmppFacade instance
	 */
	public IXmppFacade getBind() {
		return mBind;
	}

	/**
	 * Get the preference of the service.
	 * 
	 * @return the preference
	 */
	public SharedPreferences getServicePreference() {
		return mSettings;
	}

	/**
	 * Get the notification manager system service.
	 * 
	 * @return the notification manager service.
	 */
	public NotificationManager getNotificationManager() {
		return mNotificationManager;
	}

	/**
	 * A sort of patch from this thread:
	 * http://www.igniterealtime.org/community/thread/31118. Avoid
	 * ClassCastException by bypassing the classloading shit of Smack.
	 * 
	 * @param pm
	 *            The ProviderManager.
	 */
	private void configure(ProviderManager pm) {
		Log.d(TAG, "configure");
		// Service Discovery # Items
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#items",
				new DiscoverItemsProvider());
		// Service Discovery # Info
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#info",
				new DiscoverInfoProvider());

		// Privacy
		// pm.addIQProvider("query", "jabber:iq:privacy", new
		// PrivacyProvider());
		// Delayed Delivery only the new version
		pm.addExtensionProvider("delay", "urn:xmpp:delay",
				new DelayInfoProvider());

		// Service Discovery # Items
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#items",
				new DiscoverItemsProvider());
		// Service Discovery # Info
		pm.addIQProvider("query", "http://jabber.org/protocol/disco#info",
				new DiscoverInfoProvider());

		// Chat State
		ChatStateExtension.Provider chatState = new ChatStateExtension.Provider();
		pm.addExtensionProvider("active",
				"http://jabber.org/protocol/chatstates", chatState);
		pm.addExtensionProvider("composing",
				"http://jabber.org/protocol/chatstates", chatState);
		pm.addExtensionProvider("paused",
				"http://jabber.org/protocol/chatstates", chatState);
		pm.addExtensionProvider("inactive",
				"http://jabber.org/protocol/chatstates", chatState);
		pm.addExtensionProvider("gone",
				"http://jabber.org/protocol/chatstates", chatState);
		// capabilities
		pm.addExtensionProvider("c", "http://jabber.org/protocol/caps",
				new CapsProvider());
		// Pubsub
		pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub",
				new PubSubProvider());
		pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub",
				new ItemsProvider());
		pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub",
				new ItemsProvider());
		pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub",
				new ItemProvider());

		pm.addExtensionProvider("items",
				"http://jabber.org/protocol/pubsub#event", new ItemsProvider());
		pm.addExtensionProvider("item",
				"http://jabber.org/protocol/pubsub#event", new ItemProvider());
		pm.addExtensionProvider("event",
				"http://jabber.org/protocol/pubsub#event", new EventProvider());
		// TODO rajouter les manquants pour du full pubsub

		// PEP avatar
		pm.addExtensionProvider("metadata", "urn:xmpp:avatar:metadata",
				new AvatarMetadataProvider());
		pm.addExtensionProvider("data", "urn:xmpp:avatar:data",
				new AvatarProvider());

		// ping
		pm.addIQProvider(PingExtension.ELEMENT, PingExtension.NAMESPACE,
				PingExtension.class);
	}

	/**
	 * Listen on preference changes.
	 */
	private class PaprikaServicePreferenceListener implements
			SharedPreferences.OnSharedPreferenceChangeListener {

		/**
		 * ctor.
		 */
		public PaprikaServicePreferenceListener() {
		}

		@Override
		public void onSharedPreferenceChanged(
				SharedPreferences sharedPreferences, String key) {
			if (PaprikaApplication.USE_AUTO_AWAY_KEY.equals(key)) {
				if (sharedPreferences.getBoolean(
						PaprikaApplication.USE_AUTO_AWAY_KEY, false)) {
					mOnOffReceiverIsRegistered = true;
					registerReceiver(mOnOffReceiver, new IntentFilter(
							Intent.ACTION_SCREEN_OFF));
					registerReceiver(mOnOffReceiver, new IntentFilter(
							Intent.ACTION_SCREEN_ON));
				} else {
					mOnOffReceiverIsRegistered = false;
					unregisterReceiver(mOnOffReceiver);
				}
			}
		}
	}

	/**
	 * Listen on some Intent broadcast, ScreenOn and ScreenOff.
	 */
	private class PaprikaServiceBroadcastReceiver extends BroadcastReceiver {

		private String mOldStatus;
		private int mOldMode;

		/**
		 * Constructor.
		 */
		public PaprikaServiceBroadcastReceiver() {
		}

		@Override
		public void onReceive(final Context context, final Intent intent) {
			String intentAction = intent.getAction();
			if (intentAction.equals(Intent.ACTION_SCREEN_OFF)) {
				mOldMode = mConnection.getPreviousMode();
				mOldStatus = mConnection.getPreviousStatus();
				if (mConnection.isAuthentificated())
					mConnection.changeStatus(Status.CONTACT_STATUS_AWAY,
							mSettings.getString(
									PaprikaApplication.AUTO_AWAY_MSG_KEY,
									"Away"));
			} else if (intentAction.equals(Intent.ACTION_SCREEN_ON)) {
				if (mConnection.isAuthentificated())
					mConnection.changeStatus(mOldMode, mOldStatus);
			}
		}
	}
}
